﻿/**************************************************************
 * Copyright gt1987. All rights reserved.
 * 
 * Author: guitao(guitao@eastmoney.com) 
 * Create Date: 2020/4/14 15:39:16
 * Description: EncryptTokenService
 *          
 * Revision History:
 *      Date         Author               Description
 *              
***************************************************************/

using gt.ShortMessage.Models;
using gt.ShortMessage.Utils;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace gt.ShortMessage.Services.Impls
{
    /// <summary>
    /// 加密token
    /// 生成一个加密字符串，用于上下文验证
    /// 优点：无状态，无依赖服务端存储
    /// 缺点：加密算法要够强，否则被破解会导致安全问题。
    /// </summary>
    public class EncryptTokenService : ITokenService
    {
        private ILogger _logger;
        private IDataProtector _dataProtector;
        private readonly string _tokenSecret = "secret234234287fdf4";
        public EncryptTokenService(ILogger<EncryptTokenService> logger,
            IDataProtectionProvider dataProtectionProvider)
        {
            _logger = logger;
            _dataProtector = dataProtectionProvider.CreateProtector(nameof(EncryptTokenService));
        }

        public string CreateVerifyResultToken(string phone, string uniqueId)
        {
            //这里尝试生成一个jwt，没有敏感信息，主要用于验证
            var claims = new[] {
                new Claim(ClaimTypes.MobilePhone,phone),
                new Claim("uniqueId",uniqueId),
                new Claim("succ","true")
            };
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenSecret));
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
            var token = new JwtSecurityToken("www.gt.com", null, claims, null, DateTime.Now.AddMinutes(10), creds);
            return new JwtSecurityTokenHandler().WriteToken(token);
        }

        public bool VerifyVerifyResultToken(string token)
        {
            if (string.IsNullOrEmpty(token))
            {
                return false;
            }
            var handler = new JwtSecurityTokenHandler();
            var validateParameters = new TokenValidationParameters
            {
                ValidateAudience = false,
                ValidateIssuer = true,
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,
                ValidIssuer = "www.gt.com",
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenSecret))
            };
            SecurityToken securtyToken = null;
            var principal = handler.ValidateToken(token, validateParameters, out securtyToken);
            if (principal == null || securtyToken == null || !principal.HasClaim(x => x.Type == "succ"))
            {
                _logger.LogDebug($"invalid token:{token}");
                return false;
            }

            bool.TryParse(principal.FindFirst(x => x.Type == "succ").Value, out bool result);
            return result;
        }

        public string CreateActiveCodeToken(ActiveCode code)
        {
            var json = JsonConvert.SerializeObject(code);
            return _dataProtector.Protect(json);
        }

        public bool VerifyActiveCodeToken(string token, string code, ref ActiveCode activeCode)
        {
            string json = string.Empty;
            try
            {
                json = _dataProtector.Unprotect(token);
                activeCode = JsonConvert.DeserializeObject<ActiveCode>(json);
            }
            catch (CryptographicException ex)
            {
                _logger.LogDebug($"token:{token} unprotect failed:{ex.Message + ex.StackTrace}");
            }
            catch (Exception ex)
            {
                _logger.LogDebug($"token:{token}.error:{ex.Message + ex.StackTrace}");
            }
            if (activeCode == null) return false;
            if (activeCode.ExpiredTimeStamp < DateTimeHelper.ToTimeStamp(DateTime.Now))
            {
                _logger.LogDebug($"token {json} expired.");
                return false;
            }
            if (!string.Equals(activeCode.Code, code, StringComparison.CurrentCultureIgnoreCase))
            {
                _logger.LogDebug($"token {json} code not match {code}.");
                return false;
            }
            return true;
        }
    }
}
